extern "C" {
#include <time.h>
}
#include <exception>
#include <string>
#include <iostream>
#include "epoll.h"
#include "loop.h"
#include "async/asocket.h"

using namespace std;

void event_loop::run(bool forever) {
    running = 1;
    while(running) {
        auto start = std::chrono::system_clock::now();

        //  deal with epoll events
        auto [size, events] = ep.poll(0);
        for(int i=0; i<size; ++i) {
            auto& e = events[i];
            auto handle = epoll_events[events[i].fileno()];
            if(e.mask() & EPOLL_EVENTS::EPOLLERR) {
                handle->on_error();
            }else if(e.mask() & EPOLL_EVENTS::EPOLLRDHUP) {
                handle->on_close();
            }else if(e.mask() & (EPOLL_EVENTS::EPOLLIN | EPOLL_EVENTS::EPOLLPRI)) {
                handle->on_read();
            }else if(e.mask() & EPOLL_EVENTS::EPOLLOUT) {
                handle->on_write();
            }else {
                std::string err = std::string("event_loop unrecognize event:") + std::to_string(e.mask());
                throw std::runtime_error(err);
            }
        }

        //  deal with timeout events
        auto now = std::chrono::system_clock::now();
        size = time_events.size();
        while(size && time_events[0].timepoint <= now) {
            time_events[0].callback();
            time_events[0] = time_events[--size];
            size_t cur = 0, l = 1, r = 2;
            while(l < size) {
                auto &time_event_cur = time_events[cur];
                if(r > size) {
                    if(time_event_cur > time_events[l]) {
                        std::swap(time_event_cur, time_events[l]);
                        cur = l;
                    }else
                        break;
                }else {
                    if(time_event_cur > time_events[l]) {
                        if(time_events[l] > time_events[r]) {
                            std::swap(time_event_cur, time_events[r]);
                            cur = r;
                        }else {
                            std::swap(time_event_cur, time_events[l]);
                            cur = l;
                        }
                    }else if(time_event_cur > time_events[r]) {
                        std::swap(time_event_cur, time_events[r]);
                        cur = r;
                    }else
                        break;
                }

                l = (cur << 1) + 1;
                r = (cur << 1) + 2;
            }
        }
        time_events.resize(size);

        //  deal with user-custom events

        //  deal with thread events

        //  is run forever?
        if(!forever)
            break;

        //  update
        auto dur = minimal_cycle_period * 1000 -
                std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - start).count();
        if(dur > 0) usleep(dur);

//        cout << "run run run !" << endl;
    }
}

void event_loop::force_quit() {
    running = false;
}

void event_loop::quit() {
    force_quit();
}

void event_loop::add_socket(asocket* sock) {
    ep.mount(sock->fileno(), sock->event_mask);
    epoll_events.emplace(sock->fileno(), sock);
    sock->loop = this;
}

void event_loop::rmv_socket(int fd) {
    auto iter = epoll_events.find(fd);
    if(iter != epoll_events.end()) {
        auto *sock = iter->second;
        ep.umount(sock->fileno());
        epoll_events.erase(iter);
    }
}

void event_loop::mod_socket(int fd, int event_mask) {
    auto iter = epoll_events.find(fd);
    if(iter != epoll_events.end()) {
        ep.modify(fd, event_mask);
    }
}

void event_loop::set_timeout(int msec, event_loop::timeout_callback&& callback) {
    auto idx = time_events.size();
    decltype(idx) parent_idx = (idx>>1) - !(idx & 1);
    time_events.emplace_back(msec, std::forward<timeout_callback>(callback));
    while(idx) {
        if(time_events[idx] < time_events[parent_idx])
            std::swap(time_events[idx], time_events[parent_idx]);
        else
            break;

        idx = parent_idx;
        parent_idx = (parent_idx>>1) - !(parent_idx & 1);
    }
}

void event_loop::set_timeout(std::chrono::system_clock::time_point && t, event_loop::timeout_callback&& callback) {
    auto idx = time_events.size();
    decltype(idx) parent_idx = (idx>>1) - !(idx & 1);
    time_events.emplace_back(std::forward<decltype(t)>(t), std::forward<timeout_callback>(callback));
    while(idx) {
        if(time_events[idx] < time_events[parent_idx])
            std::swap(time_events[idx], time_events[parent_idx]);
        else
            break;

        idx = parent_idx;
        parent_idx = (parent_idx>>1) - !(parent_idx & 1);
    }
}
